Skip to content

refactor(engine): add fingerprinted browser leases#2159

Merged
jrusso1020 merged 2 commits into
mainfrom
07-10-refactor_engine_add_browser_leases
Jul 16, 2026
Merged

refactor(engine): add fingerprinted browser leases#2159
jrusso1020 merged 2 commits into
mainfrom
07-10-refactor_engine_add_browser_leases

Conversation

@jrusso1020

@jrusso1020 jrusso1020 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

Replace partial browser pooling with immutable, fingerprinted browser leases.

Why

Acquisition could observe the wrong launch configuration or race a browser that was already closing, and construction failure could leak ownership.

How

Model launching/ready/closing entries, remove closing instances from availability, return idempotent leases, and roll back partial sessions.

Test plan

  • browser lease pool, browser manager race, and frame-capture construction tests
  • Stack-wide lint, format, build, typecheck, and relevant integration gates

jrusso1020 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch from 1dd7199 to 82cdfe7 Compare July 13, 2026 22:18
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch from a9e5a31 to 8233fbc Compare July 13, 2026 23:08
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch 2 times, most recently from 5f2aacb to 489aa3c Compare July 14, 2026 00:03
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch 2 times, most recently from 164a96f to da86e33 Compare July 14, 2026 03:20
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch from 489aa3c to 78ee37c Compare July 14, 2026 03:20
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch from da86e33 to 75ea6e2 Compare July 14, 2026 17:03
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch from 78ee37c to 263a1a1 Compare July 14, 2026 17:03
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch from 75ea6e2 to d60af99 Compare July 14, 2026 17:11
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch 2 times, most recently from 406ba14 to a53a699 Compare July 14, 2026 17:35
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch from d60af99 to 6fa41a0 Compare July 14, 2026 17:35
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch from a53a699 to ae6769c Compare July 14, 2026 17:57
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch from 3e52184 to 703384e Compare July 14, 2026 18:06
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch from ae6769c to 3335e20 Compare July 14, 2026 18:06
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch from 703384e to 83eb853 Compare July 14, 2026 19:37
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch from 3335e20 to a1743df Compare July 14, 2026 19:37
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch from 83eb853 to 46506e3 Compare July 14, 2026 19:42
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch from a1743df to 446f86f Compare July 14, 2026 19:42
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch from 46506e3 to f4c0202 Compare July 14, 2026 19:47
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_engine_add_browser_leases branch from 446f86f to 248ec97 Compare July 14, 2026 19:47
@jrusso1020 jrusso1020 force-pushed the 07-10-refactor_render_add_execution_context branch from f4c0202 to e1c07c4 Compare July 14, 2026 19:55

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The immutable fingerprint and entry state machine are strong: browserLeasePool.ts:44-59 snapshots every launch-defining input into one canonical key, and browserLeasePool.ts:205-222 removes a closing generation from availability before awaiting teardown. The exact-identity Studio migration at studioServer.ts:167-223 also closes the previously reported cross-owner release race.

  • blocker - packages/engine/src/services/browserManager.ts:459-466 / packages/engine/src/services/browserLeasePool.ts:110-114: the still-public releaseBrowser(browser) API now silently does nothing for every browser returned by pooled acquireBrowser(). That API remains exported from @hyperframes/engine, its signature still accepts Browser, and the engine README still teaches callers to release the acquired browser through it; existing consumers therefore continue to compile but leak the lease/browser. Exact lease identity is the right new invariant, but silently returning false behind a Promise<void> is not a safe compatibility path. Preserve the unambiguous legacy case (for example, release the sole active lease and fail loudly when multiple owners make a browser handle ambiguous), or make the breaking contract explicit and migrate/document it with a regression that proves old callers cannot silently leak.

  • blocker - packages/engine/src/services/frameCapture.ts:1042-1056: construction rollback awaits page.close() without the 5-second close guard already required by normal session teardown. If page bootstrap fails after newPage() and Chromium never settles page.close()--the exact failure class waitForCloseWithTimeout handles at frameCapture.ts:3402-3409--the catch block never reaches browserLease.release(), so createCaptureSession() hangs and retains the lease indefinitely. Put lease release in a guaranteed finally path and apply the same timeout/force-release policy; pin it with a never-resolving page.close() regression.

Freshness note: the reviewed head is 7d4534e08; CI is 45 successful / 0 pending / 0 failed, but main advanced to dbb684fa6 during review. The merge is conflict-free, though this repository will need the normal restack/fresh-CI cycle after these fixes.

Verdict: REQUEST CHANGES
Reasoning: The fingerprinted lease mechanism fixes the original sharing race, but one public release path silently loses ownership and the new construction rollback can hang before releasing it; both are resource-lifetime violations in the contract this PR introduces.

-- Magi

@jrusso1020

Copy link
Copy Markdown
Collaborator Author

Addressed both ownership blockers on exact head 2290ee404, rebased onto current main@dbb684fa6 (behind_by: 0).

  • releaseBrowser(browser) now preserves the sole-active-lease legacy path and throws when multiple pooled owners make handle ownership ambiguous; regressions cover both cases. Ambiguous forceReleaseBrowser retains its existing safe no-op behavior so it cannot kill another owner's browser.
  • Construction rollback now guards page.close() with the existing 5s timeout in a guaranteed cleanup path and force-releases the exact lease on timeout. A fake-timer regression pins a never-resolving page.close().
  • Updated the engine README to teach exact BrowserLease.release() ownership.

Validation: focused ownership suite 38/38; full engine suite 1,092 passed / 3 skipped; engine typecheck + build; changed-file lint + format; pre-commit gates; and full workspace build on the exact restacked top branch. PRs #2159-#2174 are republished and fresh CI is running.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] packages/engine/src/services/frameCapture.ts:1069-1074 still cannot force-close a browser whose graceful close hangs. browserLease.release() synchronously deactivates the lease before awaiting entry.closePromise (browserLeasePool.ts:194-207). When the 5s guard expires, browserLease.forceRelease() calls deactivate(true), but active is already false, so it returns at line 195 and never invokes the pool force-close path. The warning says the process is being forced down, but the Chromium process remains alive and the pool close promise remains unresolved. The new regression only covers a never-resolving page.close(); it does not exercise a never-resolving browser.close(), which is the branch at 1069-1074. Please make force release able to escalate an already-started graceful close (or force-close the browser handle after timeout) and add the corresponding hung-browser.close() regression. The same lease-level issue also affects normal closeCaptureSession, which uses the same release-then-force pattern.

The sole-owner releaseBrowser(browser) compatibility fix is correct, and the page-close timeout test closes the first blocker. This remaining escalation hole is the same resource-lifetime invariant on the next cleanup step.

Verdict: REQUEST CHANGES
Reasoning: Exact-handle ownership is repaired, but a hung graceful browser close still makes the advertised force-release path a no-op, leaving Chromium alive after the timeout.

— Magi

@jrusso1020

Copy link
Copy Markdown
Collaborator Author

Addressed the remaining hung-browser.close() ownership blocker on exact head c5507aae0, rebased onto current main@75eedf5cc (behind_by: 0, merge base exact).

  • BrowserLease.forceRelease() can now escalate an already-started graceful close after release() has deactivated the lease, while still refusing to force-close a browser held by other active leases.
  • The pool races graceful close against a one-shot forced-close signal, so escalation both kills/disconnects Chromium and allows the pending release/entry cleanup to settle.
  • Added a fake-timer construction-rollback regression where browser.close() never resolves; it verifies the graceful close is attempted once and the browser is forcibly disconnected after the 5s guard.

Local validation on the latest restack: ownership 39/39; full engine 1,093 passed / 3 skipped; CLI download timeout 1/1; talking-head media contract 1/1; skill lint and manifest checks clean; full workspace build passed on the exact restacked top branch. PRs #2159#2174 are republished and fresh CI is running.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh exact-head re-review of d4cfa08cb646270a139ab37bde99e21b7fd49cc3 on main@3bb26b0f0 (behind_by: 0).

The remaining ownership blocker is closed. browserLeasePool.ts:208-216 now lets an inactive final lease escalate the graceful close it already started, while the entry.refCount === 0 gate prevents that stale lease from killing a browser still held by another owner. browserLeasePool.ts:224-255 turns that escalation into a one-shot signal that force-closes Chromium and resolves the pending close/entry cleanup. The construction regression at frameCapture-constructionOwnership.test.ts:107-148 exercises the actual never-resolving browser.close() path and proves the graceful attempt is followed by forced disconnect; the suite teardown also could not complete if the pool close promise remained stuck.

The earlier fixes remain intact: sole-owner releaseBrowser(browser) compatibility is preserved, ambiguous multi-owner handle release fails rather than consuming another lease, Studio retains exact thumbnail lease identity, and construction rollback applies the guarded page/browser cleanup on every failure path. I also re-enumerated production acquireBrowser/release sites; frame capture and Studio both retain and release the exact lease.

All exact-head required checks are green, including the eight regression shards, aggregate regression, Windows, Producer, CodeQL, and Graphite mergeability.

Important, non-blocking docs follow-up: packages/engine/src/index.ts:20-23 still says cleanup functions “never throw” and names releaseBrowser, but ambiguous handle release now intentionally rejects. The new behavior is the safe one; please update that error-convention comment so the public contract matches it.

Verdict: APPROVE
Reasoning: Exact lease ownership, hung-close escalation, multi-owner safety, and cleanup settlement are now coherent and regression-covered; no resource-lifetime blocker remains.

— Magi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants